﻿using System;
using System.CodeDom.Compiler;
using System.Reflection;

namespace PDO.Utilities
{
    /// <summary>
    /// General purpose expression evaluator using JScript.
    /// </summary>
    public static class ExpressionEvaluator
    {
        #region Private Members

        private static object _evaluator;
        private static Type _evaluatorType;
        private static readonly string _jscriptSource =
              @"package Evaluator
                  {
                     class Evaluator
                     {
                           public function Eval(expr : String) : String 
                           { 
                              return eval(expr); 
                           }
                     }
                  }";

        #endregion

        #region Constructor
        static ExpressionEvaluator()
        {
            CodeDomProvider provider = new Microsoft.JScript.JScriptCodeProvider();

            CompilerParameters parameters;
            parameters = new CompilerParameters();
            parameters.GenerateInMemory = true;

            CompilerResults results;
            results = provider.CompileAssemblyFromSource(parameters, _jscriptSource);

            Assembly assembly = results.CompiledAssembly;
            _evaluatorType = assembly.GetType("Evaluator.Evaluator");

            _evaluator = Activator.CreateInstance(_evaluatorType);
        }

        #endregion

        #region Public Static Methods

        /// <summary>
        /// Evaluates provided statement as a boolean expression.
        /// Caller should handle exceptions.
        /// </summary>
        /// <param name="statement">statement to evaulate</param>
        /// <returns>result of expression evaluation</returns>
        public static bool EvalToBool(string statement, bool defaultValue)
        {
            string s = EvalToString(statement);

            bool result;
            if (!bool.TryParse(s, out result))
            {
                result = defaultValue;
            }
            return result;
        }

        /// <summary>
        /// Evaluates provided statement as an integer expression.
        /// Caller should handle exceptions.
        /// </summary>
        /// <param name="statement">statement to evaulate</param>
        /// <returns>result of expression evaluation</returns>
        public static int EvalToInteger(string statement, int defaultValue)
        {
            string s = EvalToString(statement);

            int result;
            if (!int.TryParse(s, out result))
            {
                result = defaultValue;
            }
            return result;
        }

        /// <summary>
        /// Evaluates provided statement as a double expression.
        /// Caller should handle exceptions.
        /// </summary>
        /// <param name="statement">statement to evaulate</param>
        /// <returns>result of expression evaluation</returns>
        public static double EvalToDouble(string statement, double defaultValue)
        {
            string s = EvalToString(statement);

            double result;
            if (!double.TryParse(s, out result))
            {
                result = defaultValue;
            }
            return result;
        }

        /// <summary>
        /// Evaluates provided statement as a string expression.
        /// Caller should handle exceptions.
        /// </summary>
        /// <param name="statement">statement to evaulate</param>
        /// <returns>result of expression evaluation</returns>
        public static string EvalToString(string statement)
        {
            object o = EvalToObject(statement);
            return o.ToString();
        }

        /// <summary>
        /// Evaluates provided statement.
        /// Caller should handle exceptions.
        /// </summary>
        /// <param name="statement">statement to evaulate</param>
        /// <returns>result of expression evaluation</returns>
        public static object EvalToObject(string statement)
        {
            try
            {
                return _evaluatorType.InvokeMember(
                                  "Eval",
                                  BindingFlags.InvokeMethod,
                                  null,
                                  _evaluator,
                                  new object[] { statement }
                            );
            }
            catch(TargetInvocationException ex)
            {
                return "<Evaluation Error>";
            }
        }
        #endregion

    }
}
